﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using SoftCircuits;

namespace pcalc
{
	public partial class MainForm : Form
	{
		CommandList m_cmdList = new CommandList();
		RichConsole m_console;

		Font m_cmdFont = new Font("Courier New", 12, FontStyle.Bold);
		Font m_txtFont = new Font("新宋体", 9, FontStyle.Regular);

		Eval m_eval = new Eval();
		double m_result;

		public MainForm()
		{
			InitializeComponent();
			InitializeEvaluator(m_eval);
			m_console = new RichConsole(textBox_History);
		}

		private void InitializeEvaluator(Eval eval)
		{
			eval.ProcessSymbol += ProcessSymbol;
			eval.ProcessFunction += ProcessFunction;
		}

		private void OutputCommandLine(string line)
		{
			m_console.ResetStyle();
			m_console.AppendLine("");
			m_console.FontStyle = FontStyle.Bold;
			m_console.Font = m_cmdFont;
			m_console.Bullet = true;
			m_console.AppendLine(line);
		}

		private void OutputCommandLineFormat(string format, params object[] args)
		{
			OutputCommandLine(string.Format(format, args));
		}

		private void OutputString(Color color, string line)
		{
			m_console.FontStyle = FontStyle.Regular;
			m_console.Font = m_txtFont;
			m_console.Color = color;
			m_console.Bullet = false;
			m_console.Append(line);
		}

		private void OutputString(string str)
		{
			OutputString(Color.Black, str);
		}

		private void OutputStringFormat(Color color, string format, params object[] args)
		{
			OutputString(color, string.Format(format, args));
		}

		private void OutputStringFormat(string format, params object[] args)
		{
			OutputStringFormat(Color.Black, format, args);
		}

		private void OutputErrorString(string str)
		{
			OutputString(Color.Red, str);
		}

		private void OutputSeperateLine()
		{
			OutputString("--------------------------------------------------------------------\r\n");
		}

		public int GetStringWidth(string s)
		{
			if ("测试".Length == 4)
				return s.Length;

			int length = 0;
			foreach (char c in s)
			{
				if (c > 255)
					length += 2;
				else
					length++;
			}
			return length;
		}

		public string PadStringLeft(string s, int totalLength)
		{
			int len = GetStringWidth(s);

			if (len < totalLength)
			{
				string space = new string(' ', totalLength - len);
				return space + s;
			}
			else
			{
				return s;
			}
		}

		private void DisplayDataSet(DataSet dataSet, List<int> sizes)
		{
			const int DEFAULT_WIDTH = 12;
			int width;

			foreach (DataTable table in dataSet.Tables)
			{
				for (int i = 0; i < table.Columns.Count; i++)
				{
					DataColumn col = table.Columns[i];
					if (sizes != null && i < sizes.Count)
						width = sizes[i];
					else
						width = DEFAULT_WIDTH;
					OutputString(PadStringLeft(col.ToString(), width));
				}

				OutputString("\n");
				OutputSeperateLine();

				foreach (DataRow row in table.Rows)
				{
					for (int i = 0; i < row.ItemArray.Length; i++)
					{
						object obj = row.ItemArray[i];
						if (sizes != null && i < sizes.Count)
							width = sizes[i];
						else
							width = DEFAULT_WIDTH;
						OutputString(PadStringLeft(obj.ToString(), width));
					}
					OutputString("\n");
				}
			}
		}

		private void SetInputText(string expr)
		{
			if (expr == null)
				textBox_Input.Text = string.Empty;
			else
				textBox_Input.Text = expr;

			textBox_Input.SelectAll();
		}

		private void ProcessCommand(string cmd)
		{
			if (cmd.Length > 0)
			{
				if (!ProcessBuiltinCommand(cmd))
				{
					try
					{
						if ("+-*/^".Contains(cmd.Trim()[0]))
							cmd = m_result + cmd;

						OutputCommandLine(cmd);

						m_result = m_eval.Execute(cmd);
						OutputString("   " + m_result.ToString() + "\r\n");
					}
					catch (Exception e)
					{
						OutputErrorString(e.Message);
						OutputString("\n");
					}
				}
			}
		}

		private bool ProcessBuiltinCommand(string cmd)
		{
			try
			{
				string[] args = cmd.ToLower().Split(new char[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries);
				switch (args[0])
				{
					case "price":
						OutputCommandLine(cmd);
						Command_PrintChanges(args);
						break;
					case "invest":
						OutputCommandLine(cmd);
						Command_Invest(args);
						break;
					case "keys":
						OutputCommandLine(cmd);
						Command_KeyPoints(args);
						break;
					case "fib":
						OutputCommandLine(cmd);
						Command_Fibonacci(args);
						break;
					case "rmb":
						OutputCommandLine(cmd);
						Command_RMB(args);
						break;
                    case "help":
                        OutputString("Help\r\n");
                        OutputSeperateLine();
                        OutputString("built-in functions:\r\n");
                        OutputString("  abs sin cos tan pow fib round sqrt diff avga avgs\r\n");
                        OutputString("routines:\r\n");
                        OutputString("  price invest keys fib rmb help\r\n");
                        OutputString("Constants:\r\n");
                        OutputString("  pi e g\r\n");
                        break;
					default:
						return false;
				}
			}
			catch (Exception e)
			{
				OutputErrorString(e.ToString());
			}

			return true;
		}


		private void textBox_Input_KeyDown(object sender, KeyEventArgs e)
		{
			switch (e.KeyCode)
			{
				case Keys.Enter:
					m_cmdList.AddCommand(textBox_Input.Text);
					ProcessCommand(textBox_Input.Text);
					SetInputText("");
					e.Handled = true;
					break;

				case Keys.Up:
					SetInputText(m_cmdList.GetPrevCommand());
					e.Handled = true;
					break;

				case Keys.Down:
					SetInputText(m_cmdList.GetNextCommand());
					e.Handled = true;
					break;

				case Keys.Escape:
					SetInputText("");
					m_cmdList.ResetIndex();
					e.Handled = true;
					break;
			}
		}

		private void Command_PrintChanges(string[] args)
		{
			try
			{
				double p;
				double step = 0.01;

				if (args.Length != 2 || !double.TryParse(args[1], out p) || p <= 0)
				{
					OutputErrorString("Usage: price v\n");
					OutputErrorString("  v - the base price (should be greater than 0\n");
					return;
				}

				OutputString(string.Format("{0,8}{1,8}{2,8}{3,8}{4,8}{5,9}\r\n", "Percent", "Change", "Low", "High", "Change", "Percent"));
				OutputSeperateLine();

				int i = 0;
				double hi, lo, pct;

				for (i = 0; i <= 10; i++)
				{
					pct = step * i;
					if (p > 0)
					{
						hi = p * (1 + step * i);
						lo = p * (1 - step * i);
					}
					else
					{
						lo = p * (1 + step * i);
						hi = p * (1 - step * i);
					}

					// Blue
					OutputStringFormat(Color.Blue, "{0,8:P1}{1,8:F2}{2,8:F2}", -pct, Math.Abs(p - lo), lo);

					// Red
					OutputStringFormat(Color.Red, "{0,8:F2}{1,8:F2}{2,9:P1}\r\n", hi, Math.Abs(hi - p), pct);
				}
			}
			catch (Exception e)
			{
				OutputErrorString(e.ToString());
			}
		}

		private void Command_KeyPoints(string[] args)
		{
			try
			{
				double v1, v2 = 0.0;

				if (args.Length < 2 || !double.TryParse(args[1], out v1) || (args.Length >= 3 && !double.TryParse(args[2], out v2)) || v1 == v2)
				{
					OutputErrorString("Usage: keypoints v\n");
					OutputErrorString("  v1 - the price start at\n");
					OutputErrorString("  v2 - the price end with (should not equal to v1)\n");
					return;
				}

				double diff = Math.Abs(v2 - v1);
				double[] keys = { 0.000, 0.191, 0.382, 0.500, 0.618, 0.809, 1.000 };
				v1 = Math.Min(v1, v2);

				OutputStringFormat("{0,12}{1,12}{2,12}\n", "Pos", "Value", "NetChg");
				OutputSeperateLine();
				for (int i = keys.Length - 1; i >= 0; i--)
				{
					double key = keys[i];
					OutputStringFormat("{0,12:P1}{1,12:F2}{2,12:F2}\n", key, v1 + key * diff, key * diff);
				}
			}
			catch (Exception e)
			{
				OutputErrorString(e.ToString());
			}
		}

		private void Command_Invest(string[] args)
		{
			try
			{
				bool need_help = false;
				const int N = 10;
				double v1, v2;

				if (args.Length != 3 || !double.TryParse(args[1], out v1) || !double.TryParse(args[2], out v2))
				{
					need_help = true;
				}
				else
				{
					if (v1 != v2 && Math.Min(v1, v2) > 0 && Math.Max(v1, v2) < 100)
					{
						double diff = v2 - v1;
						double init = 100000;
						double cash = init;
						int shares = 0;

						OutputString(string.Format("{0,6}{1,8}{2,10}{3,6}{4,8}{5,8}{6,10}\r\n", "Price", "Buy", "Cost", "Avg", "Shares", "Loss", "Percent"));
						OutputSeperateLine();

						for (int i = 0; i <= N; i++)
						{
							double money = cash / (N + 1 - i);
							double price = (v1 + diff * i / N);

							int n = (int)Math.Floor(money / price) / 100 * 100;
							cash -= price * n;
							shares += n;

							double avg = (init - cash) / shares;
							double loss = price * shares + cash - init;

							OutputString(string.Format("{0,6:F2}{1,8:D}{2,10:F2}{3,6:F2}{4,8:D}{5,8:D}{6,10:P1}\r\n", price, n, price * n, avg, shares, (int)loss, loss / init));
						}
					}
					else
					{
						need_help = true;
					}
				}

				if (need_help)
				{
					OutputErrorString("Usage: invest v1 v2\n");
					OutputErrorString("  v1 - the price start at\n");
					OutputErrorString("  v2 - the price end with (should not equal to v1)\n");
				}
			}
			catch (Exception e)
			{
				OutputErrorString(e.ToString());
			}
		}

		private void Command_Fibonacci(string[] args)
		{
			try
			{
				for (int i = 1; i <= 10; i++)
				{
					OutputString(string.Format("{0,6}", i));
					if (i % 10 == 0) OutputString("\n");
				}

				OutputSeperateLine();

				for (int i = 1; i <= 20; i++)
				{
					OutputString(string.Format("{0,6}", fibonacci(i)));
					if (i % 10 == 0) OutputString("\n");
				}
			}
			catch (Exception e)
			{
				OutputErrorString(e.ToString());
			}
		}

		private void Command_RMB(string[] args)
		{
			try
			{
				double rmb;
				if (args.Length != 2 || !double.TryParse(args[1], out rmb))
				{
					OutputErrorString("Usage: rmb number\n");
					return;
				}

				OutputString(new RMBString(rmb) + "\n");
			}
			catch (Exception e)
			{
				OutputErrorString(e.ToString());
			}
		}

		long fibonacci(int n)
		{
			long first = 0, second = 1, next = 0;
			int i;

			for (i = 0; i <= n; i++)
			{
				if (i <= 1)
				{
					next = i;
				}
				else
				{
					next = first + second;
					first = second;
					second = next;
				}
			}

			return next;
		}


		// Implement expression symbols
		protected void ProcessSymbol(object sender, SymbolEventArgs e)
		{
			switch (e.Name.ToLower())
			{
				case "pi":
					e.Result = Math.PI;
					break;
				case "e":
					e.Result = Math.E;
					break;
				case "g":
					e.Result = 2 / (1 + Math.Sqrt(5));
					break;
				default:
					e.Status = SymbolStatus.UndefinedSymbol;
					break;
			}
		}

		// Implement expression functions
		protected void ProcessFunction(object sender, FunctionEventArgs e)
		{
			List<double> p = e.Parameters;
			switch (e.Name.ToLower())
			{
				case "abs":
					if (p.Count == 1)
						e.Result = Math.Abs(p[0]);
					else
						e.Status = FunctionStatus.WrongParameterCount;
					break;
				case "sin":
					if (p.Count == 1)
						e.Result = Math.Sin(p[0]);
					else
						e.Status = FunctionStatus.WrongParameterCount;
					break;
				case "cos":
					if (p.Count == 1)
						e.Result = Math.Cos(p[0]);
					else
						e.Status = FunctionStatus.WrongParameterCount;
					break;
				case "tan":
					if (p.Count == 1)
						e.Result = Math.Tan(p[0]);
					else
						e.Status = FunctionStatus.WrongParameterCount;
					break;
				case "pow":
					if (p.Count == 2)
						e.Result = Math.Pow(p[0], p[1]);
					else
						e.Status = FunctionStatus.WrongParameterCount;
					break;
				case "fib":
					if (p.Count == 1)
						e.Result = fibonacci((int)p[0]);
					else
						e.Status = FunctionStatus.WrongParameterCount;
					break;
				case "round":
					if (p.Count == 1)
						e.Result = Math.Round(p[0]);
					else
						e.Status = FunctionStatus.WrongParameterCount;
					break;
				case "sqrt":
					if (p.Count == 1)
						e.Result = Math.Sqrt(p[0]);
					else
						e.Status = FunctionStatus.WrongParameterCount;
					break;
				case "diff":
					if (p.Count == 2)
						e.Result = (p[1] - p[0]) / p[0];
					else
						e.Status = FunctionStatus.WrongParameterCount;
					break;
				case "avga":
					if (p.Count == 4)
						e.Result = (p[0] * p[1] + p[2] * p[3]) / (p[1] + p[3]);
					else
						e.Status = FunctionStatus.WrongParameterCount;
					break;
				case "avgs":
					if (p.Count == 4)
						e.Result = (p[0] * p[1] - p[2] * p[3]) / (p[1] - p[3]);
					else
						e.Status = FunctionStatus.WrongParameterCount;
					break;
				default:
					e.Status = FunctionStatus.UndefinedFunction;
					break;
			}
		}

		private void MainForm_Activated(object sender, EventArgs e)
		{
			textBox_Input.Focus();
		}

		private void textBox_History_KeyDown(object sender, KeyEventArgs e)
		{
			if (e.KeyCode == Keys.Escape)
				textBox_Input.Focus();
		}

	}
}
